﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Infinity.Common;
using Infinity.Common.Extensions;
using Infinity.Common.Logger;
using Infinity.Compilers;
using Infinity.Expressions;
using Infinity.Expressions.ExpressionFactory;
using Infinity.Expressions.Utilities;
using Infinity.Statements.Block;
using Infinity.Statements.Command;
using Infinity.Statements.Condition;
using Infinity.Statements.Declare;
using Infinity.Statements.Loop;
using Infinity.Statements.Program;
using Infinity.Statements.StatementFactory.Block;
using Infinity.Statements.StatementFactory.Command;
using Infinity.Statements.StatementFactory.Condition;
using Infinity.Statements.StatementFactory.Declare;
using Infinity.Statements.StatementFactory.Loop;
using Infinity.Statements.StatementFactory.Program;
using Infinity.Statements.Utilities;
using Infinity.Types;

namespace Infinity.Statements.StatementFactory
{
	/// <summary>
	/// Factory class for creating statements
	/// </summary>
	public class StatementFactory : IStatementFactory
	{
		#region Private properties

		private ILogger m_Logger;

		internal Stack<IScope> ScopeStack;
		private ICompiler m_Compiler;

		private IScopeManager m_ScopeManager
		{
			get { return m_Compiler.ScopeManager; }
		}

		private ITypeManager m_TypeManager
		{
			get { return m_Compiler.TypeManager; }
		}

		private ICastManager m_CastManager
		{
			get { return m_Compiler.CastManager; }
		}

		#endregion

		#region Public properties

		public ILogger Logger
		{
			get { return m_Logger; }
			set
			{
				if (value == null)
					throw new Exception("Logger can not be null!");
				m_Logger = value;
			}
		}

		public IExpressionFactory ExpressionFactory { get; set; }

		public List<IProgramStatement> SymbolTable { get; set; }

		public List<Assembly> AssemblyCollection { get; set; }

		public List<Dictionary<string, IStatement>> Scope { get; set; }

		#endregion

		#region Initialize

		/// <summary>
		/// Default constructor
		/// </summary>
		public StatementFactory(ICompiler compiler)
			: this(compiler, DebugLogger.Instance)
		{
		}

		/// <summary>
		/// Default constructor
		/// </summary>
		public StatementFactory(ICompiler compiler, ILogger logger)
		{
			Ensure.NotNull("compiler", compiler);
			m_Compiler = compiler;

			SymbolTable = new List<IProgramStatement>();
			ExpressionFactory = new ExpressionFactory(compiler, logger)
				                    {
					                    StatementFactory = this
				                    };

			AssemblyCollection = new List<Assembly>()
				                     {
					                     Assembly.Load("mscorlib")
				                     };

			m_TypeManager.Add(Assembly.Load("mscorlib"));
			m_TypeManager.Add(Assembly.GetExecutingAssembly());

			Initialize();
		}

		protected void Initialize()
		{
			var andOperator = new CustomOperatorStatement();
			andOperator.Name = "and";
			andOperator.Precedence = 5;
			andOperator.OperatorType = OperatorType.Infix;
			andOperator.OperatorDirection = OperatorDirection.Right;
			andOperator.ReturnType = typeof(Boolean);
			andOperator.Parameters.Add(new ParameterDeclareStatement()
				                           {
					                           Name = "a",
					                           ParameterType = typeof(bool)
				                           });
			andOperator.Parameters.Add(new ParameterDeclareStatement()
				                           {
					                           Name = "b",
					                           ParameterType = typeof(bool)
				                           });

			SymbolTable.Add(andOperator);

			var notOperator = new CustomOperatorStatement();
			notOperator.Name = "not";
			notOperator.Precedence = 20;
			notOperator.OperatorType = OperatorType.Prefix;
			notOperator.ReturnType = typeof(Boolean);
			notOperator.Parameters.Add(new ParameterDeclareStatement()
				                           {
					                           Name = "a",
					                           ParameterType = typeof(bool)
				                           });

			SymbolTable.Add(notOperator);

			var somethingOperator = new CustomOperatorStatement();
			somethingOperator.Name = "forall";
			somethingOperator.Precedence = 3;
			somethingOperator.OperatorType = OperatorType.Iterative;
			somethingOperator.ReturnType = typeof(Boolean);
			somethingOperator.Parameters.Add(new ParameterDeclareStatement()
				                                 {
					                                 Name = "condition",
					                                 ParameterType = typeof(bool)
				                                 });

			SymbolTable.Add(somethingOperator);

			var addOperator = new CustomOperatorStatement();
			addOperator.Name = "+";
			addOperator.Precedence = 5;
			addOperator.OperatorDirection = OperatorDirection.Left;
			addOperator.OperatorType = OperatorType.Infix;
			addOperator.ReturnType = typeof(int);
			addOperator.Parameters.Add(new ParameterDeclareStatement
				                           {
					                           Name = "a",
					                           ParameterType = typeof(double)
				                           });
			addOperator.Parameters.Add(new ParameterDeclareStatement
				                           {
					                           Name = "b",
					                           ParameterType = typeof(double)
				                           });

			SymbolTable.Add(addOperator);

			var substractOperator = new CustomOperatorStatement();
			substractOperator.Name = "-";
			substractOperator.Precedence = 5;
			substractOperator.OperatorDirection = OperatorDirection.Left;
			substractOperator.OperatorType = OperatorType.Infix;
			substractOperator.ReturnType = typeof(int);
			substractOperator.Parameters.Add(new ParameterDeclareStatement
				                                 {
					                                 Name = "a",
					                                 ParameterType = typeof(double)
				                                 });
			substractOperator.Parameters.Add(new ParameterDeclareStatement
				                                 {
					                                 Name = "b",
					                                 ParameterType = typeof(double)
				                                 });

			SymbolTable.Add(substractOperator);

			var multiplyOperator = new CustomOperatorStatement();
			multiplyOperator.Name = "*";
			multiplyOperator.Precedence = 5;
			multiplyOperator.OperatorDirection = OperatorDirection.Left;
			multiplyOperator.OperatorType = OperatorType.Infix;
			multiplyOperator.ReturnType = typeof(int);
			multiplyOperator.Parameters.Add(new ParameterDeclareStatement
				                                {
					                                Name = "a",
					                                ParameterType = typeof(double)
				                                });
			multiplyOperator.Parameters.Add(new ParameterDeclareStatement
				                                {
					                                Name = "b",
					                                ParameterType = typeof(double)
				                                });

			SymbolTable.Add(multiplyOperator);

			var powerOperator = new CustomOperatorStatement();
			powerOperator.Name = "^";
			powerOperator.Precedence = 50;
			powerOperator.OperatorDirection = OperatorDirection.Left;
			powerOperator.OperatorType = OperatorType.Infix;
			powerOperator.ReturnType = typeof(int);
			powerOperator.Parameters.Add(new ParameterDeclareStatement
				                             {
					                             Name = "a",
					                             ParameterType = typeof(double)
				                             });
			powerOperator.Parameters.Add(new ParameterDeclareStatement
				                             {
					                             Name = "b",
					                             ParameterType = typeof(double)
				                             });

			SymbolTable.Add(powerOperator);

			var greaterThanOperator = new CustomOperatorStatement();
			greaterThanOperator.Name = ">";
			greaterThanOperator.Precedence = 4;
			greaterThanOperator.OperatorDirection = OperatorDirection.Left;
			greaterThanOperator.OperatorType = OperatorType.Infix;
			greaterThanOperator.ReturnType = typeof(bool);
			greaterThanOperator.Parameters.Add(new ParameterDeclareStatement
				                                   {
					                                   Name = "a",
					                                   ParameterType = typeof(double)
				                                   });
			greaterThanOperator.Parameters.Add(new ParameterDeclareStatement
				                                   {
					                                   Name = "b",
					                                   ParameterType = typeof(double)
				                                   });

			SymbolTable.Add(greaterThanOperator);

			var sinFunction = new FunctionStatement();
			sinFunction.Name = "sin";
			sinFunction.ReturnType = typeof(double);
			sinFunction.Parameters.Add(new ParameterDeclareStatement
				                           {
					                           Name = "a",
					                           ParameterType = typeof(double)
				                           });

			SymbolTable.Add(sinFunction);

			var minFunction = new FunctionStatement();
			minFunction.Name = "min";
			minFunction.ReturnType = typeof(double);
			minFunction.Parameters.Add(new ParameterDeclareStatement
				                           {
					                           Name = "first",
					                           ParameterType = typeof(double)
				                           });
			minFunction.Parameters.Add(new ParameterDeclareStatement
				                           {
					                           Name = "second",
					                           ParameterType = typeof(double)
				                           });

			SymbolTable.Add(minFunction);

			var fFunction = new FunctionStatement();
			fFunction.Name = "f";
			fFunction.ReturnType = typeof(bool);
			fFunction.Parameters.Add(new ParameterDeclareStatement
				                         {
					                         Name = "a",
					                         ParameterType = typeof(double)
				                         });

			SymbolTable.Add(fFunction);
		}

		#endregion

		#region Assembly Management

		/// <summary>
		/// It loads assembly by name
		/// </summary>
		/// <param name="name"></param>
		public void LoadAssembly(string name)
		{
			Assembly assembly = Assembly.Load(name);

			AssemblyCollection.Add(Ensure.NotNull("assembly", assembly));

			Ensure.NotNull(m_TypeManager).Add(assembly);
		}

		/// <summary>
		/// It unloads assembly by name
		/// </summary>
		public void UnloadAssembly(string name)
		{
			Ensure.NotNull(m_TypeManager);

			var assembly = AssemblyCollection.First(x => x.FullName != name);

			if (assembly.IsNotNull())
				m_TypeManager.Remove(assembly);

			AssemblyCollection = AssemblyCollection.Where(x => x.FullName != name).ToList();
		}

		#endregion

		#region Make Statements

		public void CreateProcedure(
			string name, IList<IParameterDeclareStatement> parameters,
			IList<IVariableStatement> localVariables, IList<IStatement> commands)
		{
			Ensure.NotBlank(name);
			Ensure.NotNull(m_ScopeManager);

			var procedure = new ProcedureStatement
				                {
					                Name = name,
					                Commands = commands ?? new List<IStatement>(),
					                Parameters = parameters ?? new List<IParameterDeclareStatement>(),
					                LocalVariables = localVariables ?? new List<IVariableStatement>(),
				                };

			procedure.Scope = m_ScopeManager.CurrentScope();

			SymbolTable.Add(procedure);
		}

		public IVariableStatement[] CreateVariableDeclarations(params IVariableStatement[] variables)
		{
			Ensure.NotNull(variables);
			Ensure.NotNull(m_ScopeManager);

			var scope = m_ScopeManager.CreateScope();

			foreach (var variable in variables)
				scope.RegisterVariable(variable);

			return variables;
		}

		public IAssignmentStatement CreateAssignment(IIdentifierExpression left, IExpression right)
		{
			Ensure.NotNull("left", left);
			Ensure.NotNull("right", right);

			var variable = m_ScopeManager.GetVariable(left.Name);

			Error.ThrowExceptionIf(variable == null, string.Format("There is no '{0}' variable!", left.Name));

			if (left.ExpressionType != right.ExpressionType)
				Error.ThrowExceptionIf(variable.Type != typeof(Any) &&
				                       !m_CastManager.CanCast(left.ExpressionType, right.ExpressionType),
				                       string.Format("Cannot cast from '{0}' to '{1}'",
				                                     left.ExpressionType.FullName, right.ExpressionType.FullName));

			return new AssignmentStatement
				       {
					       Left = variable,
					       Value = right
				       };
		}

		public IVariableStatement DeclareVariable(string name, string type, IExpression value, bool isConst)
		{
			Ensure.NotBlank(name);
			Ensure.NotBlank(type);

			var variable = new VariableStatement
				               {
					               Name = name,
					               IsConst = isConst
				               };

			Type variableType;
			if (!m_TypeManager.TryResolveType(type, out variableType))
				Error.ThrowException(string.Format("There is no type of '{0}' at {1}!", type, name));

			if (value.IsNotNull() && variableType != value.ExpressionType)
			{
				Ensure.NotNull(m_CastManager);
				if (!m_CastManager.CanCast(variableType, value.ExpressionType))
					Ensure.NotNull(Logger).Error(string.Format("Indifference types: {0} -> {1}", variableType.FullName,
					                                           value.ExpressionType.FullName));
			}

			variable.Type = variableType;
			variable.Value = value;

			return variable;
		}

		public IWhileStatement CreateWhileStatement(IExpression condition, IBlockStatement body)
		{
			Ensure.NotNull(condition);
			Ensure.NotNull(body);

			if (condition.ExpressionType != typeof(bool))
				Error.ThrowException("Condition of while statement must be a logical expression!");

			return new WhileStatement
				       {
					       Condition = condition,
					       Body = body
				       };
		}

		public IBlockStatement CreateBlockStatement(params IStatement[] statementList)
		{
			Ensure.NotNull("statementList", statementList);

			return new BlockStatement
				       {
					       Commands = new List<IStatement>(statementList)
				       };
		}

		public IForeachStatement CreateForeachStatement(
			IIdentifierExpression variable, IExpression array, IBlockStatement body)
		{
			Ensure.NotNull("variable", variable);
			Ensure.NotNull("array", array);
			Ensure.NotNull("body", body);

			return new ForeachStatement
				       {
					       Array = array,
					       Variable = variable,
					       Body = body
				       };
		}

		public IForStatement CreateForStatement(
			IIdentifierExpression variable, IExpression start, IExpression end,
			IExpression by, IBlockStatement body)
		{
			Ensure.NotNull("variable", variable);
			Ensure.NotNull("start", start);
			Ensure.NotNull("end", end);
			Ensure.NotNull("by", by);
			Ensure.NotNull("body", body);

			return new ForStatement
				       {
					       Variable = variable,
					       Start = start,
					       End = end,
					       By = by,
					       Body = body
				       };
		}

		public IIfStatement CreateIfStatement(
			IExpression condition, IBlockStatement bodyStatement,
			List<IIfStatement> elseIfStatement, IBlockStatement elseStatement)
		{
			Ensure.NotNull(condition);
			Ensure.NotNull(bodyStatement);

			if (condition.ExpressionType != typeof(bool))
				Error.ThrowException("Condition of if statement must be a logical expression!");

			return new IfStatement
				       {
					       Condition = condition,
					       BodyStatement = bodyStatement,
					       ElseIfStatements = elseIfStatement ?? new List<IIfStatement>(),
					       ElseStatement = elseStatement
				       };
		}

		public IStatement CreateConstraint(IStatement statement, IConstraintStatement constraint)
		{
			Ensure.NotNull(statement);
			Ensure.NotNull(constraint);

			statement.Constraint = constraint;
			return statement;
		}

		/// <summary>
		/// It sets precondition, postcondition and invariant
		/// </summary>
		public IStatement CreateConstraint(
			IStatement statement, IPreConditionStatement preconditionStatement,
			IPostConditionStatement postconditionStatement,
			IInvariantStatement invariantStatement)
		{
			Ensure.NotNull(statement);
			Ensure.NotNull(preconditionStatement);
			Ensure.NotNull(postconditionStatement);
			Ensure.NotNull(invariantStatement);

			return CreateConstraint(statement,
			                        new ConstraintStatement
				                        {
					                        PreCondition = preconditionStatement,
					                        PostCondition = postconditionStatement,
					                        Invariant = invariantStatement
				                        });
		}

		public IConstraintStatement CreateConstraint(
			IExpression preconditionStatement, IExpression postconditionStatement,
			IExpression invariant, bool isStrictPreCondition = false,
			bool isStrictPostCondition = false, bool isStrictInvariant = false)
		{
			return new ConstraintStatement
				       {
					       Invariant = new InvariantStatement()
						                   {
							                   Condition = invariant,
							                   IsStrict = isStrictPreCondition
						                   },
					       PreCondition =
						       new PreConditionStatement()
							       {
								       Condition = preconditionStatement,
								       IsStrict = isStrictPostCondition
							       },
					       PostCondition =
						       new PostConditionStatement()
							       {
								       Condition = postconditionStatement,
								       IsStrict = isStrictInvariant
							       }
				       };
		}

		public IExpressionStatement CreateExpressionStatement(IExpression expression)
		{
			Ensure.NotNull("expression", expression);
			return new ExpressionStatement()
				       {
					       Expression = expression
				       };
		}

		/// <summary>
		/// Elvégzi a típusdeklarálást
		/// </summary>
		public ITypeDeclareStatement CreateTypeDeclareStatement(string name, string typeName)
		{
			Ensure.NotBlank(name);
			Ensure.NotBlank(typeName);

			var type = ResolveType(typeName);

			return new TypeDeclareStatement()
				       {
					       Name = name,
					       BaseType = type
				       };
		}

		public ITypeDeclareStatement CreateTypeDeclareStatement(
			string name, string localVariableName, string baseTypeName,
			IExpression condition)
		{
			Ensure.NotBlank(name);
			Ensure.NotBlank(localVariableName);
			Ensure.NotBlank(baseTypeName);
			Ensure.NotNull("condition", condition);

			var baseType = ResolveType(baseTypeName);

			return new SubTypeDeclareStatement
				       {
					       Name = name,
					       Condition = condition,
					       BaseType = baseType,
					       Identifier = new IdentifierExpression()
						                    {
							                    Name = localVariableName,
							                    ExpressionType = baseType
						                    }
				       };
		}

		#endregion

		#region Protected methods

		/// <summary>
		/// It resolves the given type name and returns.
		/// </summary>
		[Obsolete("Use TypeManager instead of this")]
		protected Type ResolveType(string typeName)
		{
			Ensure.NotBlank(typeName);
			Ensure.NotNull(m_TypeManager);

			var resolvedType = m_TypeManager.ResolveType(typeName);

			if (resolvedType.IsNull())
				throw new Exception(string.Format("'{0}' cannot be resolved!", typeName));

			return resolvedType;

			/*var lastIndexOfDot = typeName.LastIndexOf('.');
 
            //Default namespace name
		    string namespaceValue = null;

            if (lastIndexOfDot != -1)
            {
                namespaceValue = typeName.Substring(0, lastIndexOfDot);
                typeName = typeName.Remove(0, lastIndexOfDot + 1);
            }

		    Type type = null;
		    foreach (var assembly in AssemblyCollection)
		    {
		        type = assembly.GetTypes().FirstOrDefault(x => x.Namespace.IsNotNull() &&
		                                                       x.Namespace.In(namespaceValue) &&
		                                                       x.Name == typeName);
				if (type != null) break;
			}

			if (type == null) throw new Exception();
			return type;*/
		}

		#endregion
	}
}